﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace LightFactory.Extensions
{
    /// <summary>
    /// Factory based on attributes information about type substitutes
    /// </summary>
    public class MetaBasedFactory: FactoryBase
    {
        Assembly[] _Assembly = null;
        public MetaBasedFactory()
        {
            _Assembly = new Assembly[]{Assembly.GetCallingAssembly()};
        }

        public MetaBasedFactory(params Assembly[] assembly)
        {
            if (assembly == null || assembly.Length == 0)
                assembly = new Assembly[] { Assembly.GetCallingAssembly() };
            _Assembly = assembly;
        }

        protected override void InitFactory()
        {
            for (int i = 0; i < _Assembly.Length; i++)
            {
                ProcessAssembly(_Assembly[i]); 
            }
        }

        private void ProcessAssembly(Assembly assembly)
        {
            MethodInfo[] defines = new MethodInfo[5];
            var met = from m in typeof(FactoryBase).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)
                      where m.Name == "Define"
                      select m;

            foreach (var method in met)
            {
                var args = method.GetGenericArguments();
                defines[args.Length - 1] = method;
            }

            foreach (var type in assembly.GetTypes())
            {
                if (type.IsDefined(typeof(OverridesAttribute), false) && type.IsClass && !type.IsAbstract)
                {
                    var attr = (OverridesAttribute)type.GetCustomAttributes(typeof(OverridesAttribute), false).FirstOrDefault();
                    foreach (var ctor in type.GetConstructors())
                    {
                        var parameters = ctor.GetParameters();
                        if (parameters != null)
                        {
                            ParameterExpression[] lambdaParameters = null;
                            var parameterTypes =
                                (from p in parameters
                                 select p.ParameterType).ToArray();
                            Expression body = null;
                            LambdaExpression lambda = null;
                            var genericTypeValues = new Type[parameterTypes.Length + 1];

                            if (parameters.Length == 0)
                            {
                                body = Expression.Convert(Expression.New(ctor), attr.OverridedType);
                                lambda = Expression.Lambda(body);
                            }
                            else if (parameters.Length <= 4)
                            {
                                lambdaParameters = GetLambdaParameters(parameterTypes);
                                body = Expression.Convert(Expression.New(ctor, lambdaParameters), attr.OverridedType);
                                lambda = Expression.Lambda(body, lambdaParameters);
                            }
                            genericTypeValues[0] = attr.OverridedType;
                            Array.Copy(parameterTypes, 0, genericTypeValues, 1, parameterTypes.Length);

                            defines[parameters.Length].MakeGenericMethod(genericTypeValues).Invoke(this, new object[] { lambda.Compile() });
                        }
                    }
                }
            }
        }

        ParameterExpression[] GetLambdaParameters(Type[] parameters)
        {
            int i=0;
            return (from t in parameters
                    select Expression.Parameter(t, "param"+(++i))).ToArray();
        }
    }
}
